package puzzle.util;

import java.io.*;
import java.util.zip.*;
import org.dom4j.*;
import org.dom4j.io.*;
import puzzle.core.*;
import puzzle.core.tiles.*;
import org.newdawn.slick.util.*;
import org.newdawn.slick.loading.*;
import java.util.*;
import puzzle.game.*;
import puzzle.game.states.*;

public class MapStream {
  
  public final static String VERSION = "1.0.1";
  public final static String NEWLINE = System.getProperty("line.separator", "\n");
  
  private TileFactory tf = null;
  
  public MapStream(TileFactory tf) {
    this.tf = tf;
    if (this.tf==null) 
      this.tf = TileFactory.getCurrentInstance();
  }
  
  public MapStream() {
    this(null);
  }
    
  public void toXML(LevelData map, OutputStream out) throws IOException {
    OutputStreamWriter w = new OutputStreamWriter(out);
    
    Document document = DocumentHelper.createDocument();
    document.addComment(" Generated BPuz map file ");
    
    Element root = document.addElement("map")
                               .addAttribute("version", VERSION);
    
    GZIPOutputStream gzos = null;
    Base64.OutputStream b64os = null;
    BufferedOutputStream bos = null;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    
    try {
      //buffered output to speed things up
      bos = new BufferedOutputStream(baos);
      
      //encode our data into printable characters
      b64os = new Base64.OutputStream(bos, Base64.ENCODE);
      
      //use GZIP compression to make the file MUCH smaller.
      // The raw XML file    : 19,000 bytes (19.0 kb)
      // The compressed file : 500    bytes ( 0.5 kb)
      gzos  = new GZIPOutputStream(b64os);
      
      //write the object as an XML structure
      map.toXML(gzos);
    }
    //close all the streams
    finally {
      try { gzos.close(); } catch( Exception e ){}
      try { b64os.close(); } catch( Exception e ){}
      try { bos.close(); } catch( Exception e ){}
      try { baos.close(); } catch( Exception e ){}
    }
    
    String data = baos.toString();
    
    root.addElement("data")
           .addAttribute("compression", "gzip")
           .setText(data);
    
    OutputFormat format = OutputFormat.createPrettyPrint();
    format.setLineSeparator(NEWLINE); 
    XMLWriter writer = new XMLWriter(w, format);
    writer.write(document);
    w.close();
    out.close();
  }
  
  public LevelData fromXML(String ref) throws IOException, DocumentException {
    InputStream in = ResourceLoader.getResourceAsStream(ref.trim());
    return fromXML(in);
  }
  
  public LevelData fromXML(InputStream in) throws IOException, DocumentException {
    SAXReader reader = new SAXReader();
    Document document = reader.read(in);
    
    Element root = document.getRootElement();
    Element data = root.element("data");
    
    if (data==null) {
      throw new IOException("No <data> element found!");
    }
    
    String str = data.getText();
    
    GZIPInputStream gzis = null;
    Base64.InputStream b64is = null;
    BufferedInputStream bis = null;
    ByteArrayInputStream bain = new ByteArrayInputStream(str.getBytes());
    
    //the return value
    LevelData ret = null;
    
    try {
      //buffered input to speed things up
      bis   = new BufferedInputStream(bain);
      
      //decodes base64
      b64is = new Base64.InputStream(bis, Base64.DECODE);
      
      //GZIP decompression
      gzis  = new GZIPInputStream(b64is);
      
      //parses the decompressed XML file
      ret   = new LevelData(gzis, tf);
    }
    //close all the streams
    finally {
        //TODO: cleanup closing
      //try { gzis.close(); } catch( Exception e ){}
      //try { b64is.close(); } catch( Exception e ){}
      //try { bis.close(); } catch( Exception e ){}
      //try { bain.close(); } catch( Exception e ){}
    }
    
    return ret;
  }
  
  
  // map path methods
/*
<map-list>
  <map path="data/maps/test1.map"/>
  <map path="data/maps/test2.map"/>
  <map path="data/maps/tt.map"/>
  <map path="data/maps/newtests.map"/>
</map-list>
*/


  public void saveMapList(MapList list, OutputStream out) throws IOException {
    OutputStreamWriter w = new OutputStreamWriter(out);
    
    Document document = DocumentHelper.createDocument();
    document.addComment(" "+new Date().toString()+" ");
    
    Element root = document.addElement("map-list");
    
    for (MapList.Entry e : list) {
      String path = e.getPath();
      
      //saves any changes if we can
      try {
        toXML(e.getData(), new FileOutputStream(path));
      }
      catch (Exception exc) {
      }
      
      root.addElement("map")
              .addAttribute("path", path);
    }
        
    OutputFormat format = OutputFormat.createPrettyPrint();
    format.setLineSeparator(MapStream.NEWLINE);
    XMLWriter writer = new XMLWriter(w, format);
    writer.write(document);
    w.close();
    out.close();
  }
  
  @SuppressWarnings("unchecked")
  public MapList loadMapList(InputStream in) throws IOException, DocumentException {
    SAXReader reader = new SAXReader();
    Document document = reader.read(in);
    
    Element root = document.getRootElement();
    
    if (root==null) {
      throw new IOException("No <map-list> element found!");
    }
    
    List<Element> list = (List<Element>)root.elements("map");
          
    if (list==null)
      return new MapList();
       
    MapList dataList = new MapList(list.size());
    for (int i=0; i<list.size(); i++) {
      Element e = list.get(i);
      String path = e.attributeValue("path");
      
      if (path!=null&&path.length()!=0) {
        try {
          path = path.replaceAll("\\\\", "/").trim();
          InputStream mapIn = ResourceLoader.getResourceAsStream(path);
          LevelData d = this.fromXML(mapIn);
          d.path = path;
          dataList.add(new MapList.Entry(path, d));
        }
        catch (Exception exc) {
          exc.printStackTrace();
          Log.warn("Ignored map: '"+path+"'");
        }
      } //end add
    } //end map loop
    
    return dataList;
  }
}
